package client;
/**
 * 
 */


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Timer;

import security.Channel;
import security.ClientAuthenticationChannel;
import security.TCPChannel;
import util.KeyReader;

/**
 * @author Gernot Winkler (0929255)
 *
 */
public class Client {
    private static final int RECONNECT_TRY_INTERVAL = 5000;
    public static String username = "";
    
    public static ClientAddress[] clientList = new ClientAddress[0];
    
    private static boolean connected;
    private static boolean blocked = false;
    
    private static Channel channel = null;
    private static ClientListenerTCP cl_tcp = null;
    private static TimestampListener tl = null;
    private static String host;
    private static int remote_port, local_port;
    
    private static boolean running;
    
    private static Timer reconnectTimer;
    
    private static Queue<String> pendingSignedBids = new LinkedList<String>();
    
    public static void unblockClient(){
        blocked=false;
    }
    
    public static void blockClient(){
        blocked=true;
    }
    
    public synchronized static void lostConnection(){
        if (running) {
            if (connected){
                System.out.println("Server connection lost!");
                connected = false;
                try {
                    channel.close();
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
                ClientReconnectTimerTask task = new ClientReconnectTimerTask();
                reconnectTimer = new Timer();
                reconnectTimer.scheduleAtFixedRate(task, RECONNECT_TRY_INTERVAL, RECONNECT_TRY_INTERVAL);
                if (blocked){
                    unblockClient();
                }
            } else {
                System.out.println("Connection loss already detected");
            }
        } else {
            System.out.println("No Longer running, no Reconnection Try");
        }
    }
    
    public static void tryReconnect(){
        if (running)
        try {
            //System.out.println("Trying Reconnect to Server...");
            channel = new ClientAuthenticationChannel(new TCPChannel(host, remote_port));
            cl_tcp = new ClientListenerTCP(channel);
            connected = true;
            reconnectTimer.cancel();
            if (username == "") {
            	System.out.println("Connection successful!");
            } else {
	            System.out.println("Connection successful! Auto-login...");
	            String name = username;
	            username = "";
	            channel.send("!autologin " + name + ' ' + local_port);
            }
        } catch (UnknownHostException e) {
           //System.out.println(e.getMessage());
        } catch (IOException e) {
            //System.out.println(e.getMessage());
        }
    }
    
    /**
     * 
     */
    public static void updateClientList() {
    	cl_tcp.concatenatedListResponseForHmacCheck = "";
    	cl_tcp.hmacRequest = false;
    	cl_tcp.clientList = true;
    	cl_tcp.hiddenRequest = true;
    	try {
			channel.send("!getClientList");
		} catch (IOException e) {}
    }
    
    /**
     * @param args
     */
    public static void main(String[] args) {
        final String LOGIN_CMD = "\\!login \\S+";
        final String EXIT_CMD = "\\!end";
        final String LOGOUT_CMD = "\\!logout";
        final String LIST_CMD = "\\!list";
        final String CREATE_CMD = "\\!create \\d+ \\S+.*";
        final String BID_CMD = "\\!bid \\d+ \\d+\\.?\\d*";
        final String GROUPBID_CMD = "\\!groupBid \\d+ \\d+\\.?\\d*";
        final String CONFIRM_CMD = "\\!confirm \\d+ \\d+\\.?\\d* \\S+";
        
       // String username ="";
        //Socket s=null;
        //Channel channel = null;
        BufferedReader br = null;
        //ClientListener cl = null;
        //ClientListenerTCP cl_tcp = null;
        
        
        if (args.length == 5){
         host = args[0];
             try {
                remote_port = Integer.parseInt(args[1]);
                local_port = Integer.parseInt(args[2]);
                
                KeyReader kr = KeyReader.getInstance();
                kr.setPubKeyPath(args[3]);
                kr.setPriKeyPath(args[4] + "<user>.pem");
                kr.setSecKeyPath(args[4] + "<user>.key");
                 
                //s = new Socket(host, remote_port); //, InetAddress.getLocalHost(), local_port);
                channel = new ClientAuthenticationChannel(new TCPChannel(host, remote_port));
                connected = true;
                
                String str;
                br = new BufferedReader(new InputStreamReader(System.in));
                //sr = new BufferedReader(new InputStreamReader(s.getInputStream()));
                
                //cl = new ClientListener(local_port);
                cl_tcp = new ClientListenerTCP(channel);
                tl = new TimestampListener(local_port);
                //boolean loggedin = false;
                
                running =true;
               // System.out.print("> ");
                while (running ){
                    System.out.print(username+"> ");
                    
                    try {
                        running = ((str = br.readLine()) != null);
                        if (!running) {
                            //shutdown
                        } else if (blocked){
                            System.out.println("Waiting for server response of confirm...");
                        } else if (str.matches(BID_CMD)) {
                        	if (connected) {
                        		channel.send(str);
                        	} else {
                        		createSignedBid(str);
                        	}
                        } else if (str.matches(EXIT_CMD)) {
                            running = false;
                        } else if (!connected) {
                        	System.out.println("No server connection! Bids can still be placed, but without immediate feedback.");
                        }else if (str.matches(LOGIN_CMD)){
                            channel.send(str+" "+local_port);
                            //System.out.println("Sending: '"+str+" "+local_port+"'");
                        } else if (str.matches(LOGOUT_CMD)) {
                            channel.send("!logout");
                        } else if (str.matches(LIST_CMD)) {
                        	if (username != "") {
	                        	cl_tcp.concatenatedListResponseForHmacCheck = "";
	                        	cl_tcp.hmacRequest = false;
	                        	cl_tcp.clientList = false;
	                        	cl_tcp.hiddenRequest = false;
                        	}
                        	channel.send(str);
                        } else if (str.equals("!getClientList")) {
                        	if (username != "") {
	                        	cl_tcp.concatenatedListResponseForHmacCheck = "";
	                        	cl_tcp.hmacRequest = false;
	                        	cl_tcp.clientList = true;
	                        	cl_tcp.hiddenRequest = false;
                        	}
                        	channel.send(str);
                        } else if (str.matches(CREATE_CMD)||str.matches(GROUPBID_CMD)) {
                            //Send command to server
                            channel.send(str);
                        } else if (str.matches(CONFIRM_CMD)) {
                            //Send command to server
                        	channel.send(str);
                            
                            //Block client
                            blocked=true;
                            
                        } else {
                            System.out.println("Unrecognized Command!");
                        }

                    } catch (IOException e1){
                        Client.lostConnection();
                    }
                    
                }
                 
             } catch (NumberFormatException e){
                 System.out.println("Usage: java client.Client <host> <remote_tcp_port> <local_udp_port>");
             } catch (UnknownHostException e) {
               System.out.println("Error: "+e.getMessage());
            } catch (IOException e) {
               System.out.println("Error: "+e.getMessage());
            }  finally {
                
                System.out.println("Shutting Down");
                
                //SHUTDOWN, try to close everything
               /* if (cl != null){
                    cl.stopListening();
                }*/
                if (reconnectTimer != null){
                reconnectTimer.cancel();
                }
                
                if (cl_tcp != null){
                    try {
                        cl_tcp.stopListening();
                    } catch (IOException e) {
                    }
                }
                if (tl != null) {
                	tl.shutdown();
                }
                if (br != null){
                    try {
                        br.close();
                    } catch (IOException e) {
                    }
                }
                if (channel != null){
                    try {
                        channel.close();
                    } catch (IOException e) {
                    }
                }
            }
        } else {
            System.out.println("Usage: java client.Client <host> <remote_tcp_port> <local_udp_port>");
        }
    }
    
    private static void createSignedBid(String str) {
    	int numTimestamps = 0;
    	int i = 0;
    	String sign = "";
    	String[] cmd = str.split("\\s+");
    	String request = "!getTimestamp " + cmd[1] + ' ' + cmd[2];
    	while (numTimestamps < 2 && i < clientList.length) {
    		try {
        		if (!clientList[i].getUser().equals(username)) {
					Channel channel = new TCPChannel(clientList[i].getIp(), clientList[i].getPort());
					channel.send(request);
					String[] parts = channel.receive().split("\\s+");
					sign += ' ' + clientList[i].getUser() + ':' + parts[3] + ':' + parts[4];
					numTimestamps++;
        		}
				i++;
			} catch (IOException e) {}
    	}
    	if (numTimestamps == 2) {
    		pendingSignedBids.add("!signedBid " + cmd[1] + ' ' + cmd[2] + sign);
    	} else {
    		System.out.println("Could not reach enough other clients from client list to prove bid time!");
    	}
    }
    
    /**
     * Sends the stored saved bids and removes them from the queue.
     */
    public static void sendPendingSignedBids() {
    	String message;
    	while ((message = pendingSignedBids.poll()) != null) {
    		try {
				channel.send(message);
			} catch (IOException e) {
				System.out.println("a pending bid could not be sent to server");
			}
    	}
    }

}
